#include <iostream>
#include <fstream>
#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <ctime>
#include <string.h>
#include <stdio.h>

using namespace std;

//Declaracion de funciones
void agregarRuido(unsigned int* pixels, unsigned int cols, unsigned int filas,
                  bool* numgenerados, unsigned int cant, double nr);
void SaltearComentarios(FILE *fp);


int main(int argc, char* argv[]){ //imgOriginal, nr
    srand(time(0)); /* inicializo semilla para generar distintas sucesiones de numeros aleatorios en cada
                     * corrida del programa */

    char* imgOrig = argv[1];
    double nr = atof(argv[2]); // (entre 0 y 1)

    char imgCopy[strlen(imgOrig) + 1];
    strcpy(imgCopy, imgOrig);


    // Lectura del PGM:
    FILE* fp = fopen(strcat(imgOrig,".pgm"),"rb");
    unsigned int height, width;
	int primerC, segC, max, c1, c2, c3, f, c;

    primerC = fgetc(fp);
	segC = fgetc(fp);
	SaltearComentarios(fp);
	c1 = fscanf(fp, "%d", &width);
	SaltearComentarios(fp);
	c2 = fscanf(fp, "%d", &height);
	SaltearComentarios(fp);
	c3 = fscanf(fp, "%d", &max);
	cout << "Altura:" << height << endl << "Ancho:" << width << endl;
    unsigned int* image = new unsigned int[height*width];
	if (primerC != 80 || segC != 53 || c1 != 1 || c2 != 1 || c3 != 1 || max > 255)
    {
        cout << c3 << " " << max << " " << "La imagen seleccionada no tiene formato PGM.\n" << endl;
        return 1;
    }

	fgetc(fp);//Paso de largo el caracter siguiente ya que no me interesa (segun especificacion, generalmente es un newline)
    unsigned char temp;
	for (unsigned int i = 0; i < height*width; i++)
	{
            temp = fgetc(fp);
            image[i] =  temp;
	}

	fclose(fp);


	//Agregado de ruido:
	//seleccionamos el nr*100% de los pixeles (sus coordenadas) de la imagen
    unsigned int cant = nr * height * width;
    //numgenerados[i]==true si y solo si al pixel 'i' se le agrega ruido (o sea ponemos en true los pixeles q se van a modificar)
    bool* numgenerados = new bool[height*width];
    for(int k=0; k<height*width; k++){
        numgenerados[k] = false;
    }

    agregarRuido(image,width,height,numgenerados,cant,nr);


    //Escritura PGM:
	FILE* fpOut = fopen(strcat(imgCopy,"OutRuido.pgm"),"wb");

    fprintf(fpOut, "P5 ");
    fprintf(fpOut, "%d %d ",width,height);
    fprintf(fpOut,"%d ",max);

    for (f = 0; f < width*height; ++f)
    {
        fputc(image[f], fpOut);
    }

    fclose(fpOut);


    //Libero memoria dinamica:
    delete[] numgenerados;
    delete[] image;

    return 0;
}


void agregarRuido(unsigned int* pixels, unsigned int cols, unsigned int filas,
                  bool* numgenerados, unsigned int cant, double nr){
    // RAND_MAX equivale a 32767.

    /* En numgenerados pongo en true los numeros aleatorios que se fueron generando.
     *
     * Usaremos una funcion de hashing (doble, porque no tiene aglomeraciones como
     * hashing lineal y cuadratico).
     *
     * Las claves son los valores de 0 a (RAND_MAX*factor) para la funcion hash1 y
     * de 0 a RAND_MAX para la funcion hash2.
     *
     * Usamos un factor para hash1 para que la cantidad de claves exceda la longitud
     * del arreglo 'numgenerados' (mas que nada cuando las dimensiones de la imagen
     * son GRANDES, a partir de 256x256). De lo contrario, se acumularia mucho ruido
     * en la parte superior de la imagen.
     *
     * No usamos un factor para hash2, porque si la imagen es chica (64x64 o 128x128),
     * entonces factor==0, y de esta forma hash1=0 y hash2=1 siempre, con lo cual nos
     * queda hashDoble == ii % m, lo cual agrega ruido solo al sector superior de la
     * imagen (la secuencia de indexPos sera 0,1,2,3,4,5...). Es por esto entonces que
     * usamos un factor solo para hash1.
     */

    unsigned int m = cols*filas;
    unsigned int hash1;
    unsigned int hash2;
    unsigned int ii; //intentos
    unsigned int hashDoble;
    unsigned int factor = m/RAND_MAX; //para evitar que el ruido se concentre en la parte superior de la img
    for(unsigned int k=1; k<=cant; k++){
        ii=0;
        unsigned int indexPix;
        do{
            hash1 = (rand()*factor) % m; //es buena fc de hash si m es primo o multiplo de 2
            hash2 = 1 + (rand() % (m-1));
            hashDoble = (rand() + (hash1 + ii*hash2)) % m; //le agregue el primer rand() para que no ponga las lineas verticales xD
            indexPix = hashDoble;
            ii++;
        }while(numgenerados[indexPix]); //para evitar indices repetidos
        numgenerados[indexPix] = true;

        int r = (rand() % (255 - (int) (nr*255) + 1)) + nr*255; //numero al azar entre el nr% de 255 y 255
        int p = pixels[indexPix]; //valor del pixel a modificar
        int izq = p-r;
        int der = p+r;
        if (izq<0 && der>255){ //[p-r;p+r] contiene al [0,255]
            if (255-p < p) pixels[indexPix] = 0;
            else pixels[indexPix] = 255;
        }else{
            if (izq<0) //solo se pasa del 0
                pixels[indexPix] = (unsigned int) (p+r);
            else /* o sea, (der>255) || ([p-r;p+r] esta totalmente contenido en el [0,255]).
                   * En el primer caso, debo hacer (p-r). Para el segundo caso, es indistinto
                   * hacer (p+r) o (p-r) */
                pixels[indexPix] = (unsigned int) (p-r);
        }
    }
}

void SaltearComentarios(FILE *fp)
{
	int ch;
	char line[100];

	while ((ch = fgetc(fp)) != EOF && isspace(ch))
	{
	    ;
	}
	if (ch == '#')
	{
		fgets(line, sizeof(line), fp);
		SaltearComentarios(fp);
	}
	else
	{
        fseek(fp, -1, SEEK_CUR);
	}
}
